package cn.blnp.net.flowable.task.ext.worker;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.job.api.AcquiredExternalWorkerJob;
import org.flowable.job.api.ExternalWorkerJob;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3></h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/16 14:45
 */
@Slf4j
@SpringBootTest
public class WorkerTaskTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ManagementService  managementService;

    /**
     *  <h2>基本介绍</h2>
     *  <p>
     *   &emsp;&emsp;Flowable提供了外部工作者任务，允许创建由“外部工作者”获取并执行的作业（job）。外部工作者可以Java API或REST API获取该作业。这看起来类似异步服务任务，它们的不同之处在于，
     *  外部工作者任务不是由工作流引擎执行逻辑，而是由外部工作者（可以用任何语言实现）来查询作业，执行它们并将结果发送给工作流引擎。通过外部工作者任务，可以将复杂的业务逻辑委托给外部服
     *  务来处理，工作流引擎等待外部系统完成工作后再继续流程，从而提高了系统的可扩展性和灵活性。事件定义格式如下所示：
     *  <pre>
     *      {@code
     *          <serviceTask id="externalWorkerTask1" name="外部工作者任务" flowable:type="external-worker" flowable:topic="testTopic" flowable:exclusive="false"/>
     *      }
     *  </pre>
     *  &emsp;&emsp;如果要处理外部工作者作业，需要先获取和锁定它。使用Flowable的API获取并锁定外部工作者作业的示例如下：
     *  <pre>
     *      {@code
     *          List<AcquiredExternalWorkerJob> acquiredJobs = managementService.createExternalWorkerJobAcquireBuilder()
     *                                                                   .topic("testTopic", Duration.ofMinutes(30))
     *                                                                   .acquireAndLock(5, "worker-1");
     *      }
     *  </pre>
     *  &emsp;&emsp;在以上代码中，根据topic进行查询，最多获取5个作业，将作业的所有者设置为worker-1，并将该作业锁定30分钟，等待外部工作者完成任务并通知工作流引擎。通过AcquiredExternalWorkerJob的
     *  getVariables()方法可以访问流程的流程变量。如果将外部工作者任务的exclusive属性设置为true，AcquiredExternalWorkerJob将锁定这个流程实例。
     *  <pre>
     *      {@code
     *          managementService.createExternalWorkerCompletionBuilder(jobId, "worker-1")
     *                         .variable("result", "COMPLETED")
     *                         .complete();
     *      }
     *  </pre>
     *  &emsp;&emsp;在以上代码中，通过所有者worker-1完成了编号为jobId的AcquiredExternalWorkerJob，同时设置了变量result的值，流程将离开外部工作者任务继续执行。需要注意的是，继续执行将是以<b>异步方式在
     *  新事务</b>中完成的。这意味着工作流引擎会创建一个新的异步作业来完成该外部工作任务，并且当前线程会在它执行完后返回。外部工作者任务之后的操作将在同一个事务中执行，这类似常规的异步服务任务。因此，使用外部工
     *  作者任务<u>需要开启作业执行器</u>。<b>外部工作者任务必须被获取和锁定后才能被完成，否则会报FlowableIllegalArgumentException异常</b>.当执行外部工作者任务出现异常时，Flowable提供了两种方式来处理。
     *  <h3>方式一：</h3>
     *  <pre>
     *      {@code
     *          managementService.createExternalWorkerCompletionBuilder(jobId, "worker-1")
     *                  .variable("result", "FAILED")
     *                  .bpmnError("failed");
     *      }
     *  </pre>
     *  在以上代码中，将给流程变量result赋值，同时抛出一个错误码为failed的BPMNError。需要注意的是，只有获取了该作业的外部工作者才能抛出BPMNError。
     *  <h3>方式二：</h3>
     *  <pre>
     *      {@code
     *          managementService.createExternalWorkerJobFailureBuilder(jobId, " worker-1")
     *                          .errorMessage("执行外部工作作业异常")
     *                          .errorDetails("出现了空指针错误")
     *                          .retries(4)
     *                          .retryTimeout(Duration.ofHours(1))
     *                          .fail();
     *      }
     *  </pre>
     *  在以上代码中，errorMessage和errorDetails将被设置到该作业上，同时设置该作业的重试次数为4，将在1小时后再次被获取。该任务只能由获取它的工作者来执行。如果未设置重试次数，则采用默认值3，工作流引擎将自动将该任务的重试次数减1。
     *  当重试次数为0时，该任务将被移到DeadLetter表中，并且不再可供获取。
     *  </p>
     **/
    @Test
    public void testBase() {

    }

    /**
     *  <h2>案例演示</h2>
     *  <hr/>
     *  <p>
     *      “提交数据”用户任务办理完成后，流程流转到“外部系统执行数据归档”外部工作者任务。如果外部工作者任务正常完成，则流程流转到“数据检查”用户任务；如果外部工作者任务抛出错误，
     *  则被附属的边界错误事件捕获流转到“手动补偿归档”用户任务。
     *      <b>流程定义文件：</b>{@link flow/externalWorkerTaskProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/16/6826e0c8dcbc8.png" />
     *  </p>
     **/
    @Test
    public void testExample() throws InterruptedException {
        Deployment deploy = repositoryService.createDeployment()
                .name("外部工作者活动案例")
                .addClasspathResource("flow/externalWorkerTaskProcess.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst1 = runtimeService
                .startProcessInstanceById(procDef.getId());
        ProcessInstance procInst2 = runtimeService
                .startProcessInstanceById(procDef.getId());
        //初始化变量
        Map<String, Object> varMap1 = MapUtil.builder(new HashMap<String, Object>())
                .put("batchNo", "batch001")
                .build();
        Map<String, Object> varMap2 = MapUtil.builder(new HashMap<String, Object>())
                .put("batchNo", "batch002")
                .build();

        //查询并办理第一个任务
        Task task1_1 = taskService.createTaskQuery()
                .processInstanceId(procInst1.getId()).singleResult();
        Task task2_1 = taskService.createTaskQuery()
                .processInstanceId(procInst2.getId()).singleResult();
        taskService.complete(task1_1.getId(), varMap1);
        taskService.complete(task2_1.getId(), varMap2);

        //查询任务
        List<ExternalWorkerJob> externalWorkerJobs = managementService
                .createExternalWorkerJobQuery()
                .processDefinitionId(procDef.getId())
                .list();
        log.info("查询到的外部工作者作业数：{}", externalWorkerJobs.size());
        //获取并锁定外部工作者作业
        List<AcquiredExternalWorkerJob> acquiredJobs = managementService
                .createExternalWorkerJobAcquireBuilder()
                .topic("batchTopic", Duration.ofMinutes(30))
                .acquireAndLock(5, "worker-1");
        log.info("获取并锁定的外部工作者作业数：{}", acquiredJobs.size());
        //模拟外部工作者处理外部任务，暂停30秒
        Thread.sleep(30 * 1000);
        //第一个外部工作作业正常完成
        managementService.createExternalWorkerCompletionBuilder(acquiredJobs.get(0).getId(), "worker-1")
                .variable("result", "COMPLETED")
                .complete();
        //第二个外部工作作业抛出错误
        managementService.createExternalWorkerCompletionBuilder(acquiredJobs.get(1).getId(), "worker-1")
                .variable("result", "FAILED")
                .bpmnError("externalWorkerTaskError");
        //等待异步执行，暂停10秒
        Thread.sleep(10 * 1000);
        //查询流程当前任务
        Task task1_2 = taskService.createTaskQuery()
                .processInstanceId(procInst1.getId()).singleResult();
        Task task2_2 = taskService.createTaskQuery()
                .processInstanceId(procInst2.getId()).singleResult();
        log.info("第一个流程当前节点：{}", task1_2.getName());
        log.info("第二个流程当前节点：{}", task2_2.getName());
        //查看变量
        Map<String, Object> varMap3 = runtimeService.getVariables(procInst1.getId());
        Map<String, Object> varMap4 = runtimeService.getVariables(procInst2.getId());
        log.info("第一个流程的变量为：{}", JSON.toJSONString(varMap3));
        log.info("第二个流程的变量为：{}", JSON.toJSONString(varMap4));

        /**
         *  演示结果：
         *  2025-05-16 15:08:48.770  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 部署流程定义：9ce155c7-3224-11f0-8bb2-50ebf6ec8f6d
         * 2025-05-16 15:08:48.773  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 流程定义编号：externalWorkerTaskProcess:1:9d48f13a-3224-11f0-8bb2-50ebf6ec8f6d
         * 2025-05-16 15:08:48.915  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 查询到的外部工作者作业数：2
         * 2025-05-16 15:08:48.930  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 获取并锁定的外部工作者作业数：2
         * 2025-05-16 15:09:28.993  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 第一个流程当前节点：数据检查
         * 2025-05-16 15:09:28.993  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 第二个流程当前节点：手动补偿归档
         * 2025-05-16 15:09:29.377  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 第一个流程的变量为：{"result":"COMPLETED","batchNo":"batch001"}
         * 2025-05-16 15:09:29.378  INFO 31248 --- [           main] c.b.n.f.task.ext.worker.WorkerTaskTest   : 第二个流程的变量为：{"result":"FAILED","batchNo":"batch002"}
         **/
    }
}
